A curated repository providing GeoJSON data for Kenya's wards as established by the 2010 constitution and adopted in the 2019 census. This project serves as a practical resource for developers and GIS analysts to learn and implement spatial queries using PostGIS (for PostgreSQL) and Spatialite (for SQLite).
Data Sources:
This directory contains the Kenya ward boundary data and scripts for integrating it into both PostGIS-enabled PostgreSQL and SQLite/SpatiaLite databases using Drizzle ORM.
Our Kenya dataset includes:
This project supports two database implementations:
Located in src/drizzle-pg-postgis/ directory.
wards_geojson.ts - Complete ward boundary geometries in GeoJSON formatsub_county.ts - County-to-subcounty mappings for administrative hierarchyinsert_wards.ts - Data insertion script with geometry processingEach ward feature contains:
{
id: string, // Unique ward identifier
wardcode: string, // Official ward code
ward: string, // Ward name
county: string, // County name
countycode: number, // County code
const: string, // Constituency name
constcode: number, // Constituency code
geometry: { // MultiPolygon boundary
type: "MultiPolygon",
coordinates: number[][][][] // [polygon][ring][point][lng,lat]
}
}
Due to a known issue in Drizzle ORM, the built-in geometry() function incorrectly generates geometry(point) columns regardless of the specified type and ignores SRID configuration.
// This generates incorrect SQL: geometry(point) instead of geometry(MultiPolygon, 4326)
geometry("geometry", { type: "multipolygon", srid: 4326 });
We created custom PostGIS types in src/lib/drizzle/postgis-types.ts:
import { customType } from "drizzle-orm/pg-core";
export const multiPolygon = customType<{
data: string; // GeoJSON string
}>({
dataType() {
return "geometry(MultiPolygon, 4326)";
},
});
export const point = customType<{
data: { x: number; y: number } | string;
}>({
dataType() {
return "geometry(Point, 4326)";
},
});
export const kenyaWards = pgTable(
"kenya_wards",
{
id: serial("id").primaryKey(),
wardCode: varchar("ward_code", { length: 10 }).notNull(),
ward: text("ward").notNull(),
county: text("county").notNull(),
countyCode: integer("county_code").notNull(),
subCounty: text("sub_county"),
constituency: text("constituency").notNull(),
constituencyCode: integer("constituency_code").notNull(),
geometry: multiPolygon("geometry").notNull(), // Uses custom type
},
(t) => [
index("kenya_wards_geometry_gix").using("gist", t.geometry), // Spatial index
]
);
Located in src/sqlite-spatialite/ directory.
A pre-populated SQLite database with all Kenya ward boundaries is available at:
src/data/kenya_wards.db
This database includes:
client.ts - Database initialization with SpatiaLite extensioninsert-wards.ts - Data insertion scriptquery-wards.ts - Spatial query functionsutils.ts - Utility functions for geometry handlingconst geometry = {
type: "MultiPolygon",
coordinates: ward.coordinates, // Raw coordinates from geojson source // number[][][][] format
};
const processedWard = {
...ward,
id: parseInt(ward.id, 10),
countyCode: ward.countyCode ? ward.countyCode : -1,
constituencyCode: ward.constituencyCode ? ward.constituencyCode : -1,
subCounty: subCounty || "Unknown",
// Store the geometry as GeoJSON text - will convert to PostGIS geometry in insert
geometry: JSON.stringify(geometry),
};
const getSubCounty = (ward: string): string | null => {
const subCounty = SUB_COUNTY_MAPPINGS.find((item) => {
const subCounties = Object.values(item.sub_counties).flat();
return subCounties.some((subCountyWard) =>
subCountyWard.toLowerCase().includes(ward.toLowerCase())
);
});
return subCounty?.county_name || null;
};
// Simple batch insert - custom types handle PostGIS conversion
await db.insert(kenyaWards).values(wardsWithSubCounties);
We provide comprehensive spatial query functions in both implementations:
/**
* Find the ward that contains a given point (lat, lng)
* This is the most accurate method - checks if the point is actually inside the ward boundary
*/
export async function findWardByPoint(latitude: number, longitude: number) {
const point = sql`ST_SetSRID(ST_MakePoint(${longitude}, ${latitude}), 4326)`;
const result = await db
.select({
...getTableColumns(kenyaWards),
})
.from(kenyaWards)
.where(sql`ST_Contains(${kenyaWards.geometry}, ${point})`)
.limit(1);
return result[0] || null;
}
// Find the ward containing specific coordinates
const ward = await findWardByPoint(-1.2921, 36.8219); // Nairobi
// Find closest ward by distance
const nearestWard = await findNearestWard(-1.2921, 36.8219);
// Try exact match first, fallback to nearest
const ward = await findWardSmart(-1.2921, 36.8219);
// Find all wards within 5km
const nearbyWards = await findWardsWithinDistance(-1.2921, 36.8219, 5000);
ST_Contains(ward_geometry, point) - Point-in-polygon testST_Distance(ward_geometry, point) - Calculate distancegeometry <-> point - Fast distance operator for orderingST_DWithin(geometry, point, distance) - Distance filteringST_GeomFromGeoJSON(json) - Convert GeoJSON to PostGIS geometryST_SetSRID(geometry, 4326) - Set coordinate reference systemST_Contains(ward_geometry, point) - Point-in-polygon testST_Distance(ward_geometry, point) - Calculate distanceST_DWithin(ward_geometry, point, distance) - Distance filteringST_GeomFromGeoJSON(json) - Convert GeoJSON to SpatiaLite geometryST_SetSRID(geometry, 4326) - Set coordinate reference systemimport { findWardSmart } from "@/lib/drizzle/ward-queries";
// Example: Find ward containing Kenyatta University
const ward = await findWardSmart(-1.1677, 37.0162);
console.log(`${ward?.ward}, ${ward?.constituency}, ${ward?.county}`);
// When storing property locations
const property = {
latitude: -1.2921,
longitude: 36.8219,
locationGeom: { x: 36.8219, y: -1.2921 }, // Point geometry
};
// Find administrative context
const ward = await findWardSmart(property.latitude, property.longitude);
CREATE EXTENSION IF NOT EXISTS postgis;CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; (if using uuidgeneratev7)src/data/kenya_wards.db if it doesn't existdb.insert().values() for bulk ward insertionsgeometry()ST_MakeValid() processing-- Check geometry validity
SELECT ward, ST_IsValid(geometry), ST_IsValidReason(geometry)
FROM kenya_wards WHERE NOT ST_IsValid(geometry);
-- Verify SRID and type
SELECT ward, ST_SRID(geometry), GeometryType(geometry)
FROM kenya_wards LIMIT 5;